/*******************************************************************************
 * Copyright (c) 2010 marvin.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Public License v2.0
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 *
 * Contributors:
 *     marvin - initial API and implementation
 ******************************************************************************/

package com.marv42.ebt.newnote;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Semaphore;

import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import org.json.JSONException;
import org.json.JSONObject;

import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.Log;



public class ApiCaller
{
   private static final String EBT_API = "https://api.eurobilltracker.com/";

   private static ApiCaller mInstance = null;

   private final Semaphore mMayCall = new Semaphore(1, true);

   private final Context mContext;

   private JSONObject mResult = null;
   private String     mError  = "";



   private ApiCaller()
   {
      // ... exists only to prevent instantiation
      mContext = null;
   }



   private ApiCaller(Context context)
   {
      mContext = context;
      mError   = mContext.getString(R.string.error);
   }



   public static synchronized void
   create(Context context)
   {
      if (mInstance == null)
         mInstance = new ApiCaller(context);
   }



   public synchronized static ApiCaller
   getInstance()
   {
      return mInstance;
   }



   public Object
   clone() throws CloneNotSupportedException
   {
      throw new CloneNotSupportedException();
   }



   public String
   getError()
   {
      Log.d(EbtNewNote.LOG_TARGET, "ApiCaller.getError: " + mError);
      mMayCall.release();
      return mError;
   }



   public JSONObject
   getResult()
   {
      // Log.d(EbtNewNote.LOG_TARGET, "ApiCaller.getResult: " + mResult);
      mMayCall.release();
      return mResult;
   }



   private JSONObject
   doBasicCall(List<NameValuePair> params)
   {
      HttpResponse response = executeRequest(params);

      if (response == null)
         return null;

      int status = response.getStatusLine().getStatusCode();
      Log.d(EbtNewNote.LOG_TARGET, "status: " + Integer.toString(status) + ", " + response.getStatusLine().getReasonPhrase());
      if (status != HttpStatus.SC_OK)
      {
         mError = mContext.getString(R.string.couldnt_connect) + ": " + response.getStatusLine().getReasonPhrase() + " (" + status + ")";
         return null;
      }

      JSONObject jsonObject = getJsonObject(response);
      if (jsonObject == null)
         mError = mContext.getString(R.string.error_interpreting);
      //else Log.d(EbtNewNote.LOG_TARGET, "response: " + jsonObject.toString());

      return jsonObject;
   }



   private void
   acquire()
   {
      try
      {
         mMayCall.acquire();
      }
      catch (InterruptedException e)
      {
         Log.e(EbtNewNote.LOG_TARGET, e.getClass().getSimpleName() + ": " + e.getMessage());
      }
   }



   public boolean
   callLogin()
   {
      acquire();
      Log.d(EbtNewNote.LOG_TARGET, "ApiCaller.callLogin");

      SharedPreferences prefs =
         PreferenceManager.getDefaultSharedPreferences(mContext);

      SharedPreferences.Editor editor = prefs.edit();
      if (! editor.putBoolean(mContext.getString(R.string.pref_login_values_ok_key),
                              false)
          .commit())
         Log.e(EbtNewNote.LOG_TARGET, "editor's commit failed");

      List<NameValuePair> params = new ArrayList<NameValuePair>();
      params.add(new BasicNameValuePair("m", "login"));
      params.add(new BasicNameValuePair("v", "2"));
      params.add(new BasicNameValuePair("my_email",    prefs.getString(
         mContext.getString(R.string.pref_settings_email_key),    "").trim()));
      params.add(new BasicNameValuePair("my_password", prefs.getString(
         mContext.getString(R.string.pref_settings_password_key), "")));

      JSONObject jsonObject = doBasicCall(params);
      if (jsonObject == null)
         return false;

      if (! jsonObject.has("sessionid"))
      {
         mError = mContext.getString(R.string.wrong_password);
         return false;
      }

      Log.d(EbtNewNote.LOG_TARGET, "login successful, sessionId: " + jsonObject.optString("sessionid"));

      mResult = jsonObject;
      if (! editor.putBoolean(mContext.getString(R.string.pref_login_values_ok_key),
                              true)
                  .commit())
         Log.e(EbtNewNote.LOG_TARGET, "editor's commit failed");

      return true;
   }



   public boolean
   callInsertBills(List<NameValuePair> params)
   {
      acquire();
      Log.d(EbtNewNote.LOG_TARGET, "ApiCaller.callInsertBill");

      JSONObject jsonObject = doBasicCall(params);
      if (jsonObject == null)
         return false;

      if (! jsonObject.has("note0"))
      {
         mError = mContext.getString(R.string.server_error);
         return false;
      }

      JSONObject note0 = null;
      try
      {
         note0 = new JSONObject(jsonObject.optString("note0"));
      }
      catch (JSONException e)
      {
         Log.e(EbtNewNote.LOG_TARGET, e.getClass().getSimpleName() + ": " + e.getMessage());
      }

      if (note0 == null)
      {
         mError = mContext.getString(R.string.error_interpreting);
         return false;
      }

      if (! note0.has("status"))
      {
         mError = mContext.getString(R.string.server_error);
         return false;
      }

      mResult = note0;
      //Log.d(EbtNewNote.LOG_TARGET, "note0 status: " + note0.get("status"));
      return true;
   }



   public boolean
   callMyComments(List<NameValuePair> params)
   {
      acquire();
      Log.d(EbtNewNote.LOG_TARGET, "ApiCaller.callMyComments");

      JSONObject jsonObject = doBasicCall(params);
      if (jsonObject == null)
         return false;

      if (! jsonObject.has("rows") || ! jsonObject.has("data"))
         return false;

      mResult = jsonObject;

      return true;
   }



   private HttpResponse
   executeRequest(List<NameValuePair> params)
   {
      HttpResponse response = null;
      try
      {
         UrlEncodedFormEntity entity = new UrlEncodedFormEntity(params, HTTP.UTF_8);
         //Log.d(EbtNewNote.LOG_TARGET, "request: " + entity2String(entity));
         // don't write the password to LogCat

         HttpPost request = new HttpPost(EBT_API);
         request.setEntity(entity);

         HttpClient httpClient = new DefaultHttpClient();
         HttpProtocolParams.setUseExpectContinue(httpClient.getParams(), false);

         response = httpClient.execute(request);
      }
      catch (UnsupportedEncodingException e)
      {
         Log.e(EbtNewNote.LOG_TARGET, e.getClass().getSimpleName() + ": " + e.getMessage());
         mError = e.getClass().getSimpleName() + ": " + e.getMessage();
      }
      catch (ClientProtocolException e)
      {
         Log.e(EbtNewNote.LOG_TARGET, e.getClass().getSimpleName() + ": " + e.getMessage());
         mError = e.getClass().getSimpleName() + ": " + e.getMessage();
      }
      catch (IOException e)
      {
         Log.e(EbtNewNote.LOG_TARGET, e.getClass().getSimpleName() + ": " + e.getMessage());
         mError = e.getClass().getSimpleName() + ": " + e.getMessage();
      }

      return response;
   }



   private JSONObject
   getJsonObject(HttpResponse response)
   {
      JSONObject jsonObject = null;

      String sResponse = "";
      try
      {
         sResponse = EntityUtils.toString(response.getEntity());
      }
      catch (IOException e)
      {
         Log.e(EbtNewNote.LOG_TARGET, e.getClass().getSimpleName() + ": " + e.getMessage());
         mError = mContext.getString(R.string.error_interpreting);
      }

      if (sResponse.startsWith("ï»¿"))
      {
         Log.w(EbtNewNote.LOG_TARGET, "Server response starts with a BOM");
         sResponse = sResponse.substring(3);
      }

      if (sResponse.equals("false"))
      {
         Log.e(EbtNewNote.LOG_TARGET, "response entity: false");
         mError = mContext.getString(R.string.server_error);
         // ... assuming there was no error in our request
      }
      else
      {
         //Log.d(EbtNewNote.LOG_TARGET, "response entity: " + sResponse);

         try
         {
            jsonObject = new JSONObject(sResponse);
         }
         catch (JSONException e)
         {
            Log.e(EbtNewNote.LOG_TARGET, e.getClass().getSimpleName() + ": " + e.getMessage());
            mError = mContext.getString(R.string.error_interpreting);
         }
      }

      return jsonObject;
   }



   // convenient for debugging...
//   private static String
//   entity2String(HttpEntity entity)
//   {
//      StringBuilder sb = new StringBuilder();
//      try
//      {
//         InputStream content = entity.getContent();
//
//         String line;
//         BufferedReader br = new BufferedReader(new InputStreamReader(content));
//         while ((line = br.readLine()) != null)
//         {
//            sb.append(line);
//         }
//      }
//      catch (IOException e)
//      {
//         Log.e(EbtNewNote.LOG_TARGET, e.getClass().getSimpleName() + ": " + e.getMessage());
//         return e.getClass().getSimpleName() + ": " + e.getMessage();
//      }
//      catch (IllegalStateException e)
//      {
//         Log.e(EbtNewNote.LOG_TARGET, e.getClass().getSimpleName() + ": " + e.getMessage());
//         return e.getClass().getSimpleName() + ": " + e.getMessage();
//      }
//
//      return sb.toString();
//   }
}
